# -*- coding: utf-8 -*-
#dimension.py
#Copyright 2010 vvs <skochko@gmail.com>

import os
import datetime
import gzip
import accuracyclass
from dimmath import *
from functions import *
from ConfigParser import ConfigParser


#TEMPLATE_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)),'data/template.ini')
TEMPLATE_FILE = 'template.ini'
#print TEMPLATE_FILE


class Dim(object):
    '''
    Реализует тип данных размер с допуском
    a = Dim(5, +0.1, -0.2)
    '''
    def __init__(self, name, value=0, up_tolerance=0, un_tolerance=0, accuracy='', note=''):
        '''
        Создает число с допуском
        '''
        self.__name = name
        self.__note = note
        self.__value = value
        self.__accuracy = accuracy
        self.__up_tolerance = up_tolerance
        self.__un_tolerance = un_tolerance
        self.accuracy = accuracy

    def __str__(self):
        '''
        Создает представление размера в виде:
        Dim(value, up_tolerance, un_tolerance)
        '''
        
        return (mystr(self.__value, zero=True)+' '+
                self.accuracy+'('+
                mystr(self.__up_tolerance,sign=True, zero=True)+','+
                mystr(self.__un_tolerance,sign=True,zero=True)+')')
        #~ if self.__accuracy == '':
            #~ if self.__note != '':
                #~ return 'Dim(\''+self.__name + '\',' +\
                            #~ mystr(self.__value, True, True) + ',' + \
                            #~ mystr(self.__up_tolerance, True, True) + ',' + \
                            #~ mystr(self.__un_tolerance, True, True) + ',note=\''+ \
                            #~ self.__note + '\')'
            #~ else:
                #~ return 'Dim(\''+self.__name + '\',' +\
                            #~ mystr(self.__value, True, True) + ',' + \
                            #~ mystr(self.__up_tolerance, True, True) + ',' + \
                            #~ mystr(self.__un_tolerance, True, True) + ')'
        #~ else:
            #~ if self.__note != '':
                #~ return 'Dim(\''+self.__name + '\',' +\
                            #~ mystr(self.__value, True, True) + ',accuracy=\'' + \
                            #~ self.__accuracy + '\',note=\'' + \
                            #~ self.__note + '\')'
            #~ else:
                #~ return 'Dim(\''+self.__name + '\',' +\
                            #~ mystr(self.__value, True, True) + ',accuracy=\'' + \
                            #~ self.__accuracy + '\')'

    def __repr__(self):
        '''
        Создает представление размера в виде:
        Dim(value, up_tolerance, un_tolerance)
        '''
        if self.__accuracy == '':
            if self.__note != '':
                return 'Dim(\''+self.__name + '\',' +\
                            mystr(self.__value, True, True) + ',' + \
                            mystr(self.__up_tolerance, True, True) + ',' + \
                            mystr(self.__un_tolerance, True, True) + ',note=\''+ \
                            self.__note + '\')'
            else:
                return 'Dim(\''+self.__name + '\',' +\
                            mystr(self.__value, True, True) + ',' + \
                            mystr(self.__up_tolerance, True, True) + ',' + \
                            mystr(self.__un_tolerance, True, True) + ')'
        else:
            if self.__note != '':
                return 'Dim(\''+self.__name + '\',' +\
                            mystr(self.__value, True, True) + ',accuracy=\'' + \
                            self.__accuracy + '\',note=\'' + \
                            self.__note + '\')'
            else:
                return 'Dim(\''+self.__name + '\',' +\
                            mystr(self.__value, True, True) + ',accuracy=\'' + \
                            self.__accuracy + '\')'

    def __pos__(self):
        '''
        +x
        '''
        return Dim(self.name, self.__value, self.__up_tolerance, self.__un_tolerance)

    def __neg__(self):
        '''
        -x
        '''
        return Dim(self.name, -self.__value, -self.__un_tolerance, -self.__up_tolerance)

    def __pow__(self, other):
        'x ** y'
        x = self.__value**other
        x_max = (self.__value+self.__up_tolerance)**other
        x_min = (self.__value+self.__un_tolerance)**other
        return Dim(self.name, x, x_max-x, x_min-x)

    def __add__(self, other):
        'x + y'        
        try:
            return Dim(self.name, 
                       self.__value+other.__value,
                       self.__up_tolerance+other.__up_tolerance,
                       self.__un_tolerance+other.__un_tolerance)
        except:
            return Dim(self.name, 
                       self.__value+other,
                       self.__up_tolerance,
                       self.__un_tolerance)


    def __sub__(self, other):
        'x - y'
        try:
            x = self.__value-other.__value
            x_max = (self.__value+self.__up_tolerance)-(other.__value+other.__un_tolerance)
            x_min = (self.__value+self.__un_tolerance)-(other.__value+other.__up_tolerance)            
        except:
            x = self.__value-other
            x_max = (self.__value+self.__up_tolerance)-other
            x_min = (self.__value+self.__un_tolerance)-other
        return Dim(self.name, x, x_max-x, x_min-x)

    def __truediv__(self, other):
        'x / y'
        try:
            x = self.__value/other.__value
            x_max = (self.__value+self.__up_tolerance)/(other.__value+other.__up_tolerance)
            x_min = (self.__value+self.__un_tolerance)/(other.__value+other.__un_tolerance)            
        except:
            x = self.__value/other
            x_max = (self.__value+self.__up_tolerance)/other
            x_min = (self.__value+self.__un_tolerance)/other
        return Dim(self.name, x, x_max-x, x_min-x)

    def __mul__(self, other):
        'x * y'
        try:
            x = self.__value*other.__value
            x_max = (self.__value+self.__up_tolerance)*(other.__value+other.__up_tolerance)
            x_min = (self.__value+self.__un_tolerance)*(other.__value+other.__un_tolerance)            
        except:
            x = self.__value*other
            x_max = (self.__value+self.__up_tolerance)*other
            x_min = (self.__value+self.__un_tolerance)*other
        return Dim(self.name, x, x_max-x, x_min-x)

    def __rmul__(self, other):
        'y * x'
        try:
            x = self.__value*other.__value
            x_max = (self.__value+self.__up_tolerance)*(other.__value+other.__up_tolerance)
            x_min = (self.__value+self.__un_tolerance)*(other.__value+other.__un_tolerance)
        except:
            x = self.__value*other
            x_max = (self.__value+self.__up_tolerance)*other
            x_min = (self.__value+self.__un_tolerance)*other
        return Dim(self.name, x, x_max-x, x_min-x)
    @property
    def name(self):
        return self.__name
    @name.setter
    def name(self, name):
        self.__name = name
    
    @property
    def note(self):
        return self.__note
    
    @note.setter
    def note(self, note):
        self.__note = note

    @property
    def value(self):
        return self.__value

    @property
    def up_tolerance(self):
        return self.__up_tolerance

    @property
    def un_tolerance(self):
        return self.__un_tolerance

    @value.setter
    def value(self, value):
        self.__value = value

    @up_tolerance.setter
    def up_tolerance(self, up_tolerance):
        self.__up_tolerance = up_tolerance

    @un_tolerance.setter
    def un_tolerance(self, un_tolerance):
        self.__un_tolerance = un_tolerance
    @property
    def accuracy(self):
        return self.__accuracy
        
    @accuracy.setter
    def accuracy(self, accuracy):
        if accuracy != '':
            self.__accuracy = accuracy
            tol = accuracyclass.get_tolerance(accuracy, self.value)
            self.__up_tolerance = tol[0]
            self.__un_tolerance = tol[1]

class DimSeries(Dim):
    '''
    '''
    def __init__(self, name, note = ''):
        super(DimSeries, self).__init__(0, 0, 0)
        self.name = name
        self.note = note
        self.__formula = ''
        self.__dimensions = {}

    def __str__(self):
                return (mystr(abs(self.value), zero=True)+' '+
                self.accuracy+'('+
                mystr(self.up_tolerance,sign=True, zero=True)+','+
                mystr(self.un_tolerance,sign=True,zero=True)+')')


    def add(self, dim):
        '''
        Добавляет размер в список размеров размерной цепи
        '''
        if dim.name not in self.__dimensions:
            self.__dimensions.update({dim.name:dim})

    def update(self, dim):
        '''
        Обновляет размер в списке размеров
        '''
        self.__dimensions.update({dim.name:dim})
        #теперь нужно заново вычислить результат
        self.__calc__()

    def add_dim(self, dim):
        '''
        Добавляет размер в список при необходимости и добавляет его в
        формулу со знаком "+"
        '''
        try:
            name = dim.name
            self.add(dim)
        except AttributeError:
            name = dim
        if self.__formula == '':
            self.__formula += '+'+name
        else:
            self.__formula += ' + '+name
        self.__calc__()

    def sub_dim(self, dim):
        '''
        Добавляет размер в список при необходимости и добавляет его в
        формулу со знаком "-"
        '''
        try:
            name = dim.name
            self.add(dim)
        except AttributeError:
            name = dim
        if self.__formula == '':
            self.__formula += '-'+name
        else:
            self.__formula += ' - '+name

    @property
    def formulax(self):
        '''
        Формула в которой вместо всех переменных подставлены значения
        '''
        op = ('+', '-', '/', '*', ' ', '(', ')')
        start = 0
        f = self.__formula
        for dim_name, dim in self.__dimensions.items():
            pos = 0
            max_pos = -1
            while True:#подствим вместо переменных значения
                pos = f.find(dim_name, pos)
                if pos > max_pos:
                    max_pos = pos
                if pos != -1:
                    rep = False
                    if f == dim_name: #Первый случай формула состоит из одной переменной
                        rep = True
                        pos = max_pos + 1
                    elif pos == 0:
                        if f[pos+len(dim_name)] in op: #Переменная в начале строки
                            rep = True
                    elif pos+len(dim_name) == len(f): #Переменная в конце строки
                        if f[pos-1] in op:
                            rep = True
                    elif f[pos-1] in op:
                        if f[pos+len(dim_name)] in op:
                            rep = True
                    if rep:
                        f = f[:pos] + repr(dim) + f[pos+len(dim_name):len(f)]
                    pos += 1
                else:
                #    break
                    rep = False
                if pos < max_pos:
                    break
            #f = f.replace(dim_name, str(dim[0]))
            #print 'f =', f
        return f

    def dim_in_formula(self, dim_name):
        '''
        Возвращает True если размер dim_name присутствует в формуле
        '''
        op = ('+', '-', '/', '*', ' ', '(', ')')
        result = False
        f = self.formula
        pos = 0
        max_pos = 0
        while True:#ищим размер в формуле
            pos = f.find(dim_name, pos)
            if pos > max_pos:
                max_pos = pos
            if pos != -1:
                rep = False
                if f == dim_name: #Первый случай формула состоит из одной переменной
                    rep = True
                    pos = max_pos + 1
                elif pos == 0:
                    if f[pos+len(dim_name)] in op: #Переменная в начале строки
                        rep = True
                elif pos+len(dim_name) == len(f): #Переменная в конце строки
                    if f[pos-1] in op:
                        rep = True
                elif f[pos-1] in op:
                    if f[pos+len(dim_name)] in op:
                        rep = True
                if rep:
                    result = True
                pos += 1
            else:
            #    break
                rep = False
            if pos < max_pos:
                break
        return result

    def __calc__(self):
        '''
        Подставляет в формулу значения размеров и производит вычисления
        '''
        print '__calc__'
        f = self.formulax
        if f != '':
            r = eval(f)
            self.value = r.value
            self.up_tolerance = r.up_tolerance
            self.un_tolerance = r.un_tolerance

    @property
    def formula(self):
        return self.__formula

    @formula.setter
    def formula(self, formula):
        #print 'formula =', formula
        self.__formula = formula
        #пересчитаем формулу
        self.__calc__()

    @property
    def dimensions(self):
        return self.__dimensions

    @dimensions.setter
    def dimensions(self, dimensions):
        self.__dimensions = dimensions

class MainSeries():
    '''
    Массив размерных цепей
    '''
    def __init__(self, filename=TEMPLATE_FILE):
        #Свойства файла
        self.__series = {}          #Здесь будут храниться размерные цепи
        self.__dimensions = {}      #Здесь будут храниться все размеры
        self.filename = filename
        self.__saved = False        #True если изменения сохранены в файл
        self.propertis = {}
        self.load_from_file(self.filename)

    @property
    def saved(self):
        return self.__seved

    @property
    def save_as(self):
        if self.filename == 'template.ini':
            return True
        else:
            return False

    @property
    def series(self):
        return self.__series.values()

    @property
    def dimensions(self):
        return self.__dimensions

    def get_dim(self, name):
        return self.__dimensions[name]

    def __getattr__(self, attr):
        obj = self.__series[attr]
        if not obj:
            raise AttributeError('object %r has no attribute %r' % (self,attr))
        setattr(self, attr, obj)
        return obj

    def add_dim(self, dim):
        '''
        Добавляет размер в словарь
        '''
        #После добавления размера нужно проверить каждую
        #формулу каждой размерной цепи и при необходимости добавить его туда
        self.__dimensions.update({dim.name:dim})

    def add_series(self, series):
        #добавим размеры из размерной цепи в общий список
        for dim_name, dim in series.dimensions.items():
            self.__dimensions.update({dim_name:dim})
        #добавим необходимые размер в размерную цепь
        for dim_name, dim in self.__dimensions.items():
            if series.dim_in_formula(dim_name):
                series.add(dim)
        self.__series.update({series.name:series})

    #@property
    def get_series(self, name):
        '''
        Возвращает размерную цепь с именем name
        '''
        return self.__series[name]

    def update(self, series):
        '''
        Обновляет размерную цепь
        '''
        #теперь нужно проверить в какие размерные цепи входит эта цепь
        # и обновить размер
        for s in self.series:
            if dim_in_formula(s.formula, series.name):
                s.update(series.name,
                           str(series),
                           series.note)
                self.update(s)
            else:
                self.add_series(series)
        #self.__series[series.name] = series
        
    def update_dim(self, dim):
        '''
        Обновляет значение размера во всех размерных цепях
        '''
        #начнем с себя
        self.__dimensions.update({dim.name:dim})
        #теперь во всех размерных цепях
        for s in self.series:
            if dim_in_formula(s.formula, dim.name):
                s.update(dim)
                self.update(s)

    def load_from_file(self, filename):
        '''
        Загружает размерные цепи из файла
        '''
        self.filename=filename
        #для начала посмотрим сжат ли файл
        h = chr(0x1f)+chr(0x8b)
        fp = open(filename, 'r')
        fp.seek(0)
        fh = fp.read(2)
        fp.close()
        config = ConfigParser()
        if fh == h:
            fp = gzip.open(filename, 'r')
            config.readfp(fp)
        else:
            config.read(filename)
        #определим версию файла
        try:
            ver = config.get('Main', 'file_ver', 0)
        except:
            ver = '0'
        if ver == '0':
            try:
                ver = config.get('MAIN', 'file_ver', 0)
            except:
                ver = 0
        #print 'file_ver = ',ver

        if ver == '0':
            count = int(config.get('MAIN','count'))
            note = config.get('MAIN', 'Caption')
            dim_series = DimSeries('x1')

            for i in range(0,count):
                sect = 'Dimension '+str(i)
                name = config.get(sect, 'Name')+config.get(sect, 'Index')
                value = myfloat(config.get(sect, 'Value'))
                up_tol = myfloat(config.get(sect, 'up_tolerance'))
                un_tol = myfloat(config.get(sect, 'un_tolerance'))
                direction = int(config.get(sect, 'direction'))
                dim = Dim(value, up_tol, un_tol, name, value)
                if direction > 0:
                    dim_series.add_dim(dim)
                else:
                    dim_series.sub_dim(dim)
            self.add_series(dim_series)

        if ver == '2':
            series_count = int(config.get('MAIN','SeriesCount'))
            dim_count = int(config.get('MAIN','DimCount'))
            #Добавим все размеры
            for i in range(0, dim_count):
                sect = 'DIMENSION ' + str(i)
                name = config.get(sect, 'Name').decode('cp1251')
                name = name.replace('{', '')
                name = name.replace('}', '')
                name = name.replace(';', '')
                #print 'name =', name
                value = myfloat(config.get(sect, 'Formula'))
                up_tol = myfloat(config.get(sect, 'Up_tolerance'))
                un_tol = myfloat(config.get(sect, 'Un_tolerance'))
                dim = Dim(value, up_tol, un_tol)
                self.add_dim(name, dim)
            #Добавим все размерные цепи
            for i in range(0, series_count):
                sect = 'SERIES ' + str(i)
                name = config.get(sect, 'Name').decode('cp1251')
                note = config.get(sect, 'Title').decode('cp1251')
                f = config.get(sect, 'Formula')
                f = f.decode('cp1251')
                #print f
                ser = DimSeries(name, note)
                f = f.replace(' ', '')
                f = f.replace('{', '')
                f = f.replace('}', '')
                f = f.replace(';', '')
                f = unicode(f.replace('#', ' '))
                if f[1] == ' ':
                    f = f[0:1] + f[2:len(f)]
                #print f
                ser.formula = unicode(f)
                self.add_series(ser)


        if ver == '3':
            self.propertis = eval(config.get('Main', 'propertis', ''))

            for ser in config.sections():
                if ser != 'Main':
                    dim_series = DimSeries(ser)
                    dimensions = config.get(ser, 'dimensions', '{}')
                    dimensions = dimensions.replace('Dim(','\'')
                    dimensions = dimensions.replace(')','\'')
                    print 'dimensions = ',dimensions
                    dimensions = eval(dimensions)
                    for dim_name, dim in dimensions.items():
                        print 'dim =', dim
                        dim_value, up_tolerance, un_tolerance = dim[0].split(',')                        
                        dim = Dim(dim_name,
                                  myfloat(dim_value),
                                  myfloat(up_tolerance),
                                  myfloat(un_tolerance),
                                  note=dim[1])
                        print repr(dim)
                        dim_series.add_dim(dim)
                        
                    dim_series.formula = config.get(ser, 'formula', '')
                    self.add_series(dim_series)
        if ver == '4':
            self.propertis = eval(config.get('Main', 'propertis', ''))

            for i in config.options('Dimensions'):
                dim = config.get('Dimensions', i, '')
                print 'dim =', dim
                dim_name, dim_value, up_tolerance, un_tolerance, dim_accuracy, dim_note = dim.split(';')
                dim = Dim(dim_name,
                          myfloat(dim_value),
                          myfloat(up_tolerance),
                          myfloat(un_tolerance),
                          dim_accuracy,
                          dim_note)
                print repr(dim)
                self.add_dim(dim)

            for ser in config.sections():
                if (ser != 'Main') & (ser != 'Dimensions'):
                    dim_series = DimSeries(ser)
                    formula = config.get(ser, 'formula', '')
                    note = config.get(ser, 'note', '')
                    #dimensions = config.get(ser, 'dimensions', '{}')
                    for dim in self.dimensions.values():
                        if dim_in_formula(formula, dim.name):
                            dim_series.add(dim)
                    dim_series.formula = formula
                    dim_series.note = note
                    #print dim_series.formula
                    self.add_series(dim_series)

        try:
            if not fp.closed:
                fp.close
        except:
            pass

    def save_to_file(self, filename=''):
        '''
        Сохраняет размерные цепи в файл
        '''
        if filename == '':
            filename = self.filename

        if filename != 'template.ini':
            self.filename = filename
            config = ConfigParser()
            if not config.has_section('Main'):
                config.add_section('Main')
            if not config.has_section('Dimensions'):
                config.add_section('Dimensions')
            config.set('Main', 'propertis', self.propertis)
            config.set('Main', 'File_ver', 4)
            for dim in self.dimensions.values():
                config.set('Dimensions', dim.name, 
                            dim.name + ';'+
                            mystr(dim.value, zero=True) + ';'+
                            mystr(dim.up_tolerance, zero=True, sign=True)+';'+
                            mystr(dim.un_tolerance, zero=True, sign=True)+';'+
                            dim.accuracy+';'+
                            dim.note)
            for ser in self.__series.values():
                if not config.has_section(ser.name):
                    config.add_section(ser.name)
                config.set(ser.name, 'formula', ser.formula)
                config.set(ser.name, 'note', ser.note)
                #config.set(ser.name, 'dimensions', ser.dimensions)
            #fp = gzip.open(filename, 'w')
            fp = open(filename, 'w')
            config.write(fp)
            fp.close()
